﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Red.Controls.ViewModels;

namespace Red.Controls
{
    /// <summary>
    /// Explorer-like breadcrumb bar for path-based navigation
    /// </summary>
    public partial class BreadcrumbBar : UserControl
    {
        #region Data

        private string _separator = System.IO.Path.DirectorySeparatorChar.ToString();
        private readonly ObservableCollection< BreadcrumbBarItem > _items = new ObservableCollection<BreadcrumbBarItem>();
        private string _path = String.Empty;

        private readonly List<string> _history = new List<string>();
        private int _historyCursor = -1;
        private bool _historyLock = false;

        #endregion Data

        #region Properties

        /// <summary>
        ///     A character (or multiple characters) separating path elements in the Path. By default it's 
        ///     System.IO.Path.DirectorySeparatorChar
        /// </summary>
        public string Separator
        {
            get { return _separator; }

            set
            {
                if ( value != _separator )
                {
                    _separator = value;
                    ClearHistory(); // as changing separator is not a part of a history, it has to be cleared
                    RebuildItems( Path );
                }
            }
        }

        /// <summary>
        ///     Gets or sets a path shown in the BreadcrumbBar. 
        /// </summary>
        /// <remarks>
        ///     - Each path segment should be separated by Separator. For clarity, following examples assume that it is a '\'. 
        ///     - You can group multiple path segments into one element by using curly brackets, 
        ///       e.g. "{x\y}\z" will be displayed as two elements: "x\y" followed by "z". 
        ///     - You can use curly brackets also to add additional options, like displaying an icon instead of text
        ///       or displaying different text for the title and path element. Here are all possible combinations:
        ///         -- {title}
        ///         -- {title,path}
        ///         -- {title,icon:IconName}
        ///         -- {title,path,icon:IconName}
        ///       Icons have to be of type Canvas, and are looked up in the application's resource dictionary by name
        /// </remarks>
        public string Path
        {
            get { return _path; }

            set 
            {
                if ( value != null && !value.EndsWith( _separator ) )
                { // normalize to satisfy the comparison below
                    value = value + _separator;
                }

                if ( value != _path )
                { // do not rebuild if the path is the same
                    RebuildItems( value );
                    UpdateHistory( value );
                    _path = value; // store unresolved path for the getter
                }
            }
        }

        #endregion Properties

        #region C-tor

        public BreadcrumbBar()
        {
            InitializeComponent();
            DataContext = _items;
        }

        #endregion C-tor

        #region Public methods
        
        public void HistoryBack()
        {
            if ( _historyCursor > 0 )
            {
                --_historyCursor;
                ApplyHistory();
            }
        }

        public void HistoryForward()
        {
            if ( _historyCursor < _history.Count - 1 )
            {
                ++_historyCursor;
                ApplyHistory();
            }
        }
        
        #endregion Public methods

        #region Events

        public delegate void NavigateToHandler( object sender, BreadcrumbBarItem e );

        /// <summary>
        ///     An event generated when the User has used the BreadcrumbBar to navigate. Use Path property
        ///     to read the current path.
        /// </summary>
        public event NavigateToHandler NavigateTo;

        public delegate Task<List<string>> EnumerateBranchesHandler( BreadcrumbBarItem e );

        /// <summary>
        ///     An event called when the BreadcrumbBar is about to display list of "branches" (a drop-down
        ///     list next to each path element). The handler should provide possible branches by adding them to the list.
        /// </summary>
        public event EnumerateBranchesHandler EnumerateBranches;

        #endregion Events

        #region Private methods

        // rebuilds items basing on path
        private void RebuildItems( string path )
        {
            _items.Clear();
            if ( String.IsNullOrEmpty( path ) )
                return;

            string[] parts = path.Split( new[] { _separator }, StringSplitOptions.RemoveEmptyEntries );

            BreadcrumbBarItem prev = null;
            for ( int i = 0; i < parts.Length; ++i )
            {
                if ( parts[ i ].StartsWith( "{" ) )
                {
                    string compoundPart = String.Empty;
                    for ( ; i < parts.Length; ++i )
                    {
                        compoundPart += parts[ i ];
                        if ( compoundPart.EndsWith( "}" ) )
                        {
                            var item = new BreadcrumbBarItem( this, prev, compoundPart );
                            _items.Add( item );
                            prev = item;
                            break;
                        }

                        if ( i != parts.Length - 1 )
                            compoundPart += _separator;
                    }

                    if ( i == parts.Length )
                    { // closing } not found
                        return; // TODO: error handling
                    }
                }
                else
                {
                    var item = new BreadcrumbBarItem( this, prev, parts[ i ] );
                    _items.Add( item );
                    prev = item;
                }
            }
        }

        // rebuilds path basing on items
        private void RebuildPath()
        {
            _path = String.Empty;
            foreach ( BreadcrumbBarItem item in _items )
            {
                _path += item.Description + _separator;
            }
        }

        private void UpdateHistory( string path )
        {
            if ( !_historyLock )
            {
                if ( _historyCursor < _history.Count - 1 )
                {
                    _history.RemoveRange( _historyCursor + 1, _history.Count - _historyCursor - 1 ); // trim forward history
                }
                _history.Add( path );
                ++_historyCursor;
                Debug.Assert( _historyCursor == _history.Count - 1 );
            }
        }

        private void ApplyHistory()
        {
            _historyLock = true;
            Path = _history[ _historyCursor ];
            if ( _items.Count > 0 && NavigateTo != null )
                NavigateTo( this, _items[ _items.Count - 1 ] );
            _historyLock = false;
        }

        public void ClearHistory()
        {
            _history.Clear();
            _historyCursor = -1;
        }

        private void TrimAfter( BreadcrumbBarItem item )
        {
            for ( int i = _items.Count - 1; i > _items.IndexOf( item ); --i )
            {
                _items.RemoveAt( i );
            }
        }

        internal async Task<List<string>> GetBranchesForItem( BreadcrumbBarItem e )
        {
            if ( EnumerateBranches != null )
            {
                return await EnumerateBranches( e );
            }
            else
            {
                return new List<string>();
            }
        }

        #endregion Private methods

        #region Event Handlers

        private void PathElement_Click( object sender, RoutedEventArgs e )
        {
            var item = ( e.OriginalSource as MenuItem ).DataContext as BreadcrumbBarItem;
            Debug.Assert( item != null );
            TrimAfter( item );
            RebuildPath();
            UpdateHistory( _path );
            if ( NavigateTo != null ) NavigateTo( this, item );
        }

        private void Branch_Click( object sender, RoutedEventArgs e )
        {
            var item = ( e.Source as MenuItem ).DataContext as BreadcrumbBarItem;
            Debug.Assert( item != null );
            string elemText = ( e.OriginalSource as MenuItem ).Header as string;
            if ( elemText != null )
            {
                TrimAfter( item );
                var newItem = new BreadcrumbBarItem( this, item, elemText );
                _items.Add( newItem );
                RebuildPath();
                UpdateHistory( _path );
                if ( NavigateTo != null ) NavigateTo( this, newItem );
            }
        }

        private void BrowseBack_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            HistoryBack();
        }

        private void BrowseBack_CanExecute( object sender, CanExecuteRoutedEventArgs e )
        {
            e.CanExecute = _historyCursor > 0;
        }

        private void BrowseForward_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            HistoryForward();
        }

        private void BrowseForward_CanExecute( object sender, CanExecuteRoutedEventArgs e )
        {
            e.CanExecute = _historyCursor < _history.Count - 1;
        }

        #endregion
    }
}
